package Manager;
import Commons.Logging.Log;
import Commons.Logging.Logger;
import Commons.Activity;
import Commons.Address;
import Commons.Indirizzi;
import Commons.ServerTrasferimentoFileUDP;
import Commons.TaskDescriptor;
import Commons.Replicazione.RMManager;
import Commons.Replicazione.StatoManager;
import Commons.Workflow;
import Discovery.DiscoveryProxyFactory;
import Discovery.WorkerDiscoveryProxy;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import javax.xml.stream.XMLStreamException;



/** Questa classe descrive il Manager.
*
* @author Team5
*/
public class Manager implements AbstractDispatcher, Runnable {

    String id;
    /**
     * ArrayList di Activity.
     */
    private ArrayList<Activity> activities;
    private static boolean runner=true;
    private WFQueueManager wfqmanager;
    private Logger l;
    private RMManager rm;
    private ServerTrasferimentoFileUDP stfu;
    private Thread twfq;
    private Thread tthis;
    private Thread tstfu;
    /**
     * Variabile di debug per contare il numero di FW ricevuti.
     */
    private int indice=0;


    /**Costruttore di default del Manager:
     * Il costruttore crea due istanze di WFQueueManager (gestore coda di wf) e di FileReceiveManager (gestore invio file).
     * Entrambe le classi istanziate implementano runnable. Dunque avvia i due oggetti su due thread differenti.
     * Infine avvia un terzo Thread su se stesso, che gestirà il dequeue del Manager dal WFQueueManager (CS2).
     */
    public Manager(RMManager rm) throws XMLStreamException, InterruptedException, IOException, Exception{
    this.rm=rm;
    id=this.getClass().getSimpleName()+this.hashCode();
    System.out.println("Idstart:"+id);
    this.activities=new ArrayList<Activity>();
    l=new Logger("logManager.xml",this.getClass().getSimpleName());
    System.out.println("Manager(): creo un istanza del WFQueueManager");
    wfqmanager= new WFQueueManager();
    l.logging(new Log(this.getClass().getSimpleName(),id, "Creato wfqueuemanager",String.valueOf(System.currentTimeMillis())));
    System.out.println("Manager(): creo un istanza del ServerTrasferimentoFileUDP");
    stfu= new ServerTrasferimentoFileUDP("Download/",Indirizzi.portaInvioFile);
    l.logging(new Log(this.getClass().getSimpleName(),id, "Creato ServerTrasferimentoFile",String.valueOf(System.currentTimeMillis())));
    System.out.println("Manager(): avvio thread ServerQueue");
    rm.aggiornaStato(this);
    //System.out.println("L.getVectorClock() "+l.getVectorClock().get(l.getPosizioneNelVC()));
    //if(true){throw new Exception("CRASHER!!!");}
    System.out.println("START");
    twfq=new Thread(wfqmanager);
    //t1.setDaemon(true);
    //Start server gestore della coda
    twfq.start();
    tstfu=new Thread(stfu);
    //tstfu.start();



    tthis =new Thread(this);
    tthis.start();
    tthis.join();
    
    //SE IL RUN FINISCE IL THROW RISALE NEL MAINMANAGER CHE RICOSTRUISCE UN NUOVO MANAGER
    System.out.println("ERROR CLOSE RUN");
    throw new Exception("ERROR CLOSE RUN");

    }

    public Manager(StatoManager rs, RMManager rm) throws XMLStreamException, InterruptedException, Exception {
    this.rm=rm;
    id=rs.getIdManager();
    System.out.println("IdREstart:"+id);
    this.activities=new ArrayList<Activity>();
    l=new Logger("logManager.xml",this.getClass().getSimpleName(),rs.getVectorClockManager(),rs.getPosizioneVectorClockManager());
    System.out.println("VC_"+rs.getVectorClockManager().get(rs.getPosizioneVectorClockManager()));
    System.out.println("Manager(): creo un istanza del WFQueueManager");
    wfqmanager= new WFQueueManager(rs.getIdWFQ(), rs.getWfQueue(), rs.getVectorClockWFQM(),rs.getPosizioneVectorClockWFQM(),rs.getNumeroTotaleElementiInseritiWFQM());
    l.logging(new Log(this.getClass().getSimpleName(),id, "Creato wfqueuemanager",String.valueOf(System.currentTimeMillis())));
    System.out.println("Manager(): creo un istanza del ServerTrasferimentoFileUDP");
    stfu= new ServerTrasferimentoFileUDP("Download/",Indirizzi.portaInvioFile);
    l.logging(new Log(this.getClass().getSimpleName(),id, "Creato ServerTrasferimentoFile",String.valueOf(System.currentTimeMillis())));
    System.out.println("Manager(): avvio thread ServerQueue");
    
   
    System.out.println("START");
    //twfq.join();
    twfq=new Thread(wfqmanager);
    //t1.setDaemon(true);
    //Start server gestore della coda
    
    twfq.start();
    tstfu=new Thread(stfu);
    //tstfu.start();



    tthis =new Thread(this);
    tthis.start();
    tthis.join();

    System.out.println("STOPPER");
    throw new Exception("ERROR CLOSE RUN");
    }
   

   

    /**Il metodo setta l'ArrayList activities, contenente la lista delle Attività del Manager.
     *
     * @param val Lista di attività
     */
    public void setActivities(ArrayList<Activity> val){
        this.activities=val;
    }
    
    /**Metodo run() relativo al Thread del Manager che gestisce il dequeue dal WFQueueManager (CS2).
     *
     */
   

    public void run() {

        tstfu.start();
        this.runner=true;
        
        //System.out.println("Manager.run()");
        
        
        while(runner){
           

        //Entro in un ciclo while in cui ad ogni iterazione consumo un WF.
        
         /*
         * Il tread attende che ci sia almeno un WF da consumare nel WFQueueManager.
         * Questa situazione verrà notificata dall'UI quando avviene un nuovo enqueue.
         */
             System.out.println("MANAGER: mi metto in wait in attesa di un Notify()");
             synchronized(wfqmanager.getWfQueue()){

                    try{
                        //if(Math.random()<0.1){throw new Exception("CRASHER1!!!");}
                 
                    while (wfqmanager.getWfQueue().isEmpty()) {
                        // Il thread aspetta finchè arriva la notifica di un nuovo inserimento nella lista dei Workflow
                        wfqmanager.getWfQueue().wait();

                    }
                    
                    
                    System.out.println("MANAGER: sono stato svegliato-> la coda non è vuota, mi accingo a consumare");
                    try {
                        Workflow wf = wfqmanager.dequeue();
                        
                        wfqmanager.getWfQueue().notifyAll();
                        rm.aggiornaStato(this);
                       
                        l.merge(wf.getVectorClock());
                        System.out.println("MANAGER:(*) ho estratto il WF " + wf.getID());
                        l.logging(new Log(this.getClass().getSimpleName(), id, "ho estratto il WF " + wf.getID(), String.valueOf(System.currentTimeMillis())));
                        //Non appena estrae un WF parte il CS2
                        Activity activity = new Activity();
                        wf.setVectorClock(l.getVectorClock());
                        activity.setWf(wf);
                        rm.aggiornaStato(this);
                        l.logging(new Log(this.getClass().getSimpleName(), id, "Creata nuova Activity e settata con il " + wf.getID(), String.valueOf(System.currentTimeMillis())));
                        WorkerNetwork wn = new WorkerNetwork();
                        
                        DiscoveryProxyFactory dpf = new DiscoveryProxyFactory();
                        Address addr = new Address();
                        /**
                         * Iterator itera tutti i TaskDescriptor presenti nel wf
                         */
                        Iterator<TaskDescriptor> iter = wf.getTasks().iterator();
                        while (iter.hasNext()) {
                            TaskDescriptor td = iter.next();
                            System.out.println("MANAGER: invio TD: " + td.getID() + " (WF " + wf.getID() + ")");
                            l.logging(new Log(this.getClass().getSimpleName(), id, "invio TD: " + td.getID() + " (WF " + wf.getID() + ")", String.valueOf(System.currentTimeMillis())));
                            WorkerDiscoveryProxy wdp;
                            try {
                                //System.out.println("Richiedo WDP");
                                wdp = dpf.getProxy();
                                //System.out.println("Ricevuto WDP. Eseguo il getProxy()");
                                td.setVectorClock(l.getVectorClock());
                                addr = wdp.getWorker(td);

                                l.merge(addr.getVectorClock());
                                //rm.aggiornaStato(this);
                                //if(Math.random()<0.5){throw new Exception("CRASHER2!!!");}
                                //System.out.println("Inviato il TD");
                            } catch (UnknownHostException ex) {
                                System.out.println("EXCEPTION MANAGER:su wdp o dpf");
                                l.logging(new Log(this.getClass().getSimpleName(), id, "EXCEPTION MANAGER: su wdp o dpf", String.valueOf(System.currentTimeMillis())));
                            }catch(Exception ex){
                            System.out.println(ex.getMessage());
                            runner=false;
                           
                            }
                            System.out.println("MANAGER: ricevuto Address: " + addr.getIP() + " " + addr.getPort());
                            l.logging(new Log(this.getClass().getSimpleName(), id, "ricevuto Address: " + addr.getIP() + " " + addr.getPort(), String.valueOf(System.currentTimeMillis())));
                            int sizeofwn = wn.addWorker(addr);
                        }
                        //Viene settato Sbn, cioà il WorkerNetwork dell'attività
                        activity.setSbn(wn);
                        this.activities.add(activity);
                        //Inizia la comunicazione Manager -- Worker (CS3)
                        //Scheduling Activity
                        //System.out.println("MANAGER:Inizio schedulazione Activity");
                        System.out.println("MANAGER:ho estratto " + (indice = indice + 1) + " WORKFLOW ");
                        activity.scheduleActivity();
                        l.logging(new Log(this.getClass().getSimpleName(), id, "schedulata Activity " + activity.toString(), String.valueOf(System.currentTimeMillis())));
                    } catch (IndexOutOfBoundsException ex) {
                        System.out.println("MANAGER: ho trovato la coda vuota; mi metto in wait() in attesa di un Notify");
                        // this.wait();
                        System.out.println("MANAGER: sono stato svegliato-> la coda non è vuota, mi accingo a consumare");
                    }
                    }catch(InterruptedException ex) {
                    java.util.logging.Logger.getLogger(Manager.class.getName()).log(Level.SEVERE, null, ex);

                    }catch(Exception ex){
                    System.out.println(ex.getMessage());
                    
                    }finally{

                    runner=false;
                    System.out.println("Manager_finally");
                    wfqmanager.setCondizioneFalse();
                    }
                   
                    

            }
           
        
        
    }
    System.out.println("MANAGER:Esco dal Run()");
    }

    
    public Logger getLogger(){
        if(l==null){l= new Logger("logManager.xml",this.getClass().getSimpleName());}
        return this.l;
    }
    public WFQueueManager getWFQueueManager() throws XMLStreamException{
        if(wfqmanager==null){wfqmanager= new WFQueueManager();}
        return this.wfqmanager;
    }
    public RMManager getRepliactionManager(){
        return this.rm;
    }
    public ServerTrasferimentoFileUDP getServerTrasferimentoFileUDP(){
        System.out.println("RESTART STFU");
        if(stfu==null){stfu= new ServerTrasferimentoFileUDP("Download/",Indirizzi.portaInvioFile);}
        return this.stfu;
    }
     /**Il metodo ritorna la lista delle Attività del Manager.
     *
     * @return activities Lista di attività.
     */
    public ArrayList<Activity> getActivities(){
         if(activities==null){activities=new ArrayList<Activity>();}
        return this.activities;
    }
     public String getID(){
         if(this.id==null){id=this.getClass().getSimpleName()+this.hashCode();}
         return id;
    }
    
   


}

